page.tsx 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. "use client";
  2. import {fetchApi, fetchFile} from "@/app/_modules/func";
  3. import {DeleteOutlined, ExclamationCircleFilled, EyeOutlined, ReloadOutlined,} from "@ant-design/icons";
  4. import type {ActionType, ProColumns, ProFormInstance,} from "@ant-design/pro-components";
  5. import {PageContainer, ProDescriptions, ProTable,} from "@ant-design/pro-components";
  6. import {Button, Modal, Space, Tag} from "antd";
  7. import {useRouter} from "next/navigation";
  8. import {faCheck, faDownload, faToggleOff, faToggleOn, faXmark,} from "@fortawesome/free-solid-svg-icons";
  9. import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
  10. import {useRef, useState} from "react";
  11. import globalMessage from "@/app/_modules/globalMessage";
  12. //查询Job详情
  13. const queryJobAPI = "/api/monitor/job";
  14. //查询表格数据API
  15. const queryAPI = "/api/monitor/jobLog/list";
  16. //删除API
  17. const deleteAPI = "/api/monitor/jobLog";
  18. //导出API
  19. const exportAPI = "/api/monitor/jobLog/export";
  20. //导出文件前缀名
  21. const exportFilePrefix = "joblog";
  22. //清空调度日志API
  23. const clearAllAPI = "/api/monitor/jobLog/clean";
  24. export default function JobLog({ params }: { params: { jobid: string } }) {
  25. const { push } = useRouter();
  26. // 添加用于控制删除确认模态框的状态
  27. const [deleteModalVisible, setDeleteModalVisible] = useState(false);
  28. const [deleteJobLogId, setDeleteJobLogId] = useState<string | number | null>(null);
  29. // 添加用于控制清空确认模态框的状态
  30. const [clearAllModalVisible, setClearAllModalVisible] = useState(false);
  31. //获取对应的任务的JobName的值
  32. const getJobName = async () => {
  33. const resp = await fetchApi(`${queryJobAPI}/${params.jobid}`, push);
  34. if (resp != undefined) {
  35. if (searchTableFormRef.current) {
  36. searchTableFormRef.current.setFieldsValue({
  37. jobName: resp.data.jobName,
  38. jobGroup: resp.data.jobGroup,
  39. });
  40. }
  41. return [resp.data.jobName, resp.data.jobGroup];
  42. }
  43. return "";
  44. };
  45. //表格列定义
  46. const columns: ProColumns[] = [
  47. {
  48. title: "日志编号",
  49. dataIndex: "jobLogId",
  50. search: false,
  51. },
  52. {
  53. title: "任务名称",
  54. fieldProps: {
  55. placeholder: "请输入任务名称",
  56. },
  57. dataIndex: "jobName",
  58. ellipsis: true,
  59. order: 4,
  60. },
  61. {
  62. title: "任务组名",
  63. dataIndex: "jobGroup",
  64. valueType: "select",
  65. valueEnum: {
  66. DEFAULT: {
  67. text: "默认",
  68. status: "DEFAULT",
  69. },
  70. SYSTEM: {
  71. text: "系统",
  72. status: "SYSTEM",
  73. },
  74. },
  75. order: 3,
  76. },
  77. {
  78. title: "调用目标字符串",
  79. dataIndex: "invokeTarget",
  80. ellipsis: true,
  81. search: false,
  82. },
  83. {
  84. title: "日志信息",
  85. dataIndex: "jobMessage",
  86. ellipsis: true,
  87. search: false,
  88. },
  89. {
  90. title: "执行状态",
  91. dataIndex: "status",
  92. valueType: "select",
  93. render: (_, record) => {
  94. return (
  95. <Space>
  96. <Tag
  97. color={record.status === "0" ? "green" : "red"}
  98. icon={
  99. record.status == 0 ? (
  100. <FontAwesomeIcon icon={faCheck} />
  101. ) : (
  102. <FontAwesomeIcon icon={faXmark} />
  103. )
  104. }
  105. >
  106. {_}
  107. </Tag>
  108. </Space>
  109. );
  110. },
  111. valueEnum: {
  112. 0: {
  113. text: "成功",
  114. status: "0",
  115. },
  116. 1: {
  117. text: "失败",
  118. status: "1",
  119. },
  120. },
  121. order: 2,
  122. },
  123. {
  124. title: "执行时间",
  125. dataIndex: "createTime",
  126. valueType: "dateTime",
  127. search: false,
  128. },
  129. {
  130. title: "执行时间",
  131. fieldProps: {
  132. placeholder: ["开始日期", "结束日期"],
  133. },
  134. dataIndex: "createTimeRange",
  135. valueType: "dateRange",
  136. hideInTable: true,
  137. order: 1,
  138. search: {
  139. transform: (value) => {
  140. return {
  141. "params[beginTime]": `${value[0]} 00:00:00`,
  142. "params[endTime]": `${value[1]} 23:59:59`,
  143. };
  144. },
  145. },
  146. },
  147. {
  148. title: "操作",
  149. key: "option",
  150. search: false,
  151. render: (_, record) => [
  152. <Button
  153. key="detailBtn"
  154. type="link"
  155. icon={<EyeOutlined />}
  156. onClick={() => onClickShowRowDetailModal(record)}
  157. >
  158. 详情
  159. </Button>,
  160. ],
  161. },
  162. ];
  163. //0.查询表格数据
  164. const queryTableData = async (params: any, sorter: any, filter: any) => {
  165. const searchParams = {
  166. pageNum: params.current,
  167. ...params,
  168. };
  169. delete searchParams.current;
  170. const queryParams = new URLSearchParams(searchParams);
  171. //如果没有带上默认的字典类型,查询绑定上
  172. if (!("jobName" in searchParams)) {
  173. const result = await getJobName();
  174. queryParams.append("jobName", result[0]);
  175. queryParams.append("jobGroup", result[1]);
  176. }
  177. Object.keys(sorter).forEach((key) => {
  178. queryParams.append("orderByColumn", key);
  179. if (sorter[key] === "ascend") {
  180. queryParams.append("isAsc", "ascending");
  181. } else {
  182. queryParams.append("isAsc", "descending");
  183. }
  184. });
  185. const body = await fetchApi(`${queryAPI}?${queryParams}`, push);
  186. return body;
  187. };
  188. //操作当前数据的附加数据
  189. const [operatRowData, setOperateRowData] = useState<{
  190. [key: string]: any;
  191. }>({});
  192. //3.删除
  193. //点击删除按钮,展示删除确认框
  194. const onClickDeleteRow = (record?: any) => {
  195. const jobLogId = record !== undefined ? record.jobLogId : selectedRowKeys.join(",");
  196. setDeleteJobLogId(jobLogId);
  197. setDeleteModalVisible(true);
  198. };
  199. //确定删除选中的数据
  200. const executeDeleteRow = async () => {
  201. if (deleteJobLogId === null) return;
  202. const body = await fetchApi(`${deleteAPI}/${deleteJobLogId}`, push, {
  203. method: "DELETE",
  204. });
  205. if (body !== undefined) {
  206. if (body.code == 200) {
  207. globalMessage.success("删除成功");
  208. //删除按钮变回不可点击
  209. setRowCanDelete(false);
  210. //选中行数据重置为空
  211. setSelectedRowKeys([]);
  212. //刷新列表
  213. if (actionTableRef.current) {
  214. actionTableRef.current.reload();
  215. }
  216. } else {
  217. globalMessage.error(body.msg);
  218. }
  219. }
  220. setDeleteModalVisible(false);
  221. setDeleteJobLogId(null);
  222. };
  223. //取消删除操作
  224. const cancelDeleteRow = () => {
  225. setDeleteModalVisible(false);
  226. setDeleteJobLogId(null);
  227. };
  228. //弹出清空确认框
  229. const onClickClearAll = () => {
  230. setClearAllModalVisible(true);
  231. };
  232. //执行清空调度日志
  233. const executeClearAll = async () => {
  234. const body = await fetchApi(clearAllAPI, push, {
  235. method: "DELETE",
  236. });
  237. if (body !== undefined) {
  238. if (body.code == 200) {
  239. globalMessage.success("清空成功");
  240. if (actionTableRef.current) {
  241. actionTableRef.current.reload();
  242. }
  243. } else {
  244. globalMessage.error(body.msg);
  245. }
  246. } else {
  247. globalMessage.error("清空发生异常");
  248. }
  249. setClearAllModalVisible(false);
  250. };
  251. //取消清空操作
  252. const cancelClearAll = () => {
  253. setClearAllModalVisible(false);
  254. };
  255. //4.导出
  256. //导出表格数据
  257. const exportTable = async () => {
  258. if (searchTableFormRef.current) {
  259. const formData = new FormData();
  260. const data = {
  261. pageNum: page,
  262. pageSize: pageSize,
  263. ...searchTableFormRef.current.getFieldsValue(),
  264. };
  265. Object.keys(data).forEach((key) => {
  266. if (data[key] !== undefined) {
  267. formData.append(key, data[key]);
  268. }
  269. });
  270. await fetchFile(
  271. exportAPI,
  272. push,
  273. {
  274. method: "POST",
  275. body: formData,
  276. },
  277. `${exportFilePrefix}_${new Date().getTime()}.xlsx`
  278. );
  279. }
  280. };
  281. //5.选择行
  282. //选中行操作
  283. const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
  284. const [selectedRow, setSelectedRow] = useState(undefined as any);
  285. //删除按钮是否可用,选中行时才可用
  286. const [rowCanDelete, setRowCanDelete] = useState(false);
  287. //ProTable rowSelection
  288. const rowSelection = {
  289. onChange: (newSelectedRowKeys: React.Key[], selectedRows: any[]) => {
  290. setSelectedRowKeys(newSelectedRowKeys);
  291. setRowCanDelete(newSelectedRowKeys && newSelectedRowKeys.length > 0);
  292. if (newSelectedRowKeys && newSelectedRowKeys.length == 1) {
  293. setSelectedRow(selectedRows[0]);
  294. } else {
  295. setSelectedRow(undefined);
  296. }
  297. },
  298. //复选框的额外禁用判断
  299. // getCheckboxProps: (record) => ({
  300. // disabled: record.userId == 1,
  301. // }),
  302. };
  303. //搜索栏显示状态
  304. const [showSearch, setShowSearch] = useState(true);
  305. //action对象引用
  306. const actionTableRef = useRef<ActionType>(null);
  307. //搜索表单对象引用
  308. const searchTableFormRef = useRef<ProFormInstance>(null!);
  309. //当前页数和每页条数
  310. const [page, setPage] = useState(1);
  311. const defaultPageSize = 10;
  312. const [pageSize, setPageSize] = useState(defaultPageSize);
  313. const pageChange = (page: number, pageSize: number) => {
  314. setPage(page);
  315. setPageSize(pageSize);
  316. };
  317. const [isShowDetail, setIsShowDetail] = useState(false);
  318. //展示详情框
  319. const onClickShowRowDetailModal = (record: any) => {
  320. setIsShowDetail(true);
  321. setSelectedRow(record);
  322. };
  323. return (
  324. <PageContainer
  325. header={{
  326. title: "调度日志",
  327. onBack(e) {
  328. push("/monitor/job");
  329. },
  330. }}
  331. >
  332. <ProTable
  333. formRef={searchTableFormRef}
  334. rowKey="jobLogId"
  335. rowSelection={{
  336. selectedRowKeys,
  337. ...rowSelection,
  338. }}
  339. columns={columns}
  340. request={async (params: any, sorter: any, filter: any) => {
  341. // 表单搜索项会从 params 传入,传递给后端接口。
  342. const data = await queryTableData(params, sorter, filter);
  343. if (data !== undefined) {
  344. return Promise.resolve({
  345. data: data.rows,
  346. success: true,
  347. total: data.total,
  348. });
  349. }
  350. return Promise.resolve({
  351. data: [],
  352. success: true,
  353. });
  354. }}
  355. pagination={{
  356. defaultPageSize: defaultPageSize,
  357. showQuickJumper: true,
  358. showSizeChanger: true,
  359. onChange: pageChange,
  360. }}
  361. search={
  362. showSearch
  363. ? {
  364. defaultCollapsed: false,
  365. searchText: "搜索",
  366. }
  367. : false
  368. }
  369. dateFormatter="string"
  370. actionRef={actionTableRef}
  371. toolbar={{
  372. actions: [
  373. <Button
  374. key="danger"
  375. danger
  376. icon={<DeleteOutlined />}
  377. disabled={!rowCanDelete}
  378. onClick={() => onClickDeleteRow()}
  379. >
  380. 删除
  381. </Button>,
  382. <Button
  383. key="danger"
  384. danger
  385. icon={<DeleteOutlined />}
  386. onClick={() => onClickClearAll()}
  387. >
  388. 清空
  389. </Button>,
  390. <Button
  391. key="export"
  392. type="primary"
  393. icon={<FontAwesomeIcon icon={faDownload} />}
  394. onClick={exportTable}
  395. >
  396. 导出
  397. </Button>,
  398. ],
  399. settings: [
  400. {
  401. key: "switch",
  402. icon: showSearch ? (
  403. <FontAwesomeIcon icon={faToggleOn} />
  404. ) : (
  405. <FontAwesomeIcon icon={faToggleOff} />
  406. ),
  407. tooltip: showSearch ? "隐藏搜索栏" : "显示搜索栏",
  408. onClick: (key: string | undefined) => {
  409. setShowSearch(!showSearch);
  410. },
  411. },
  412. {
  413. key: "refresh",
  414. tooltip: "刷新",
  415. icon: <ReloadOutlined />,
  416. onClick: (key: string | undefined) => {
  417. if (actionTableRef.current) {
  418. actionTableRef.current.reload();
  419. }
  420. },
  421. },
  422. ],
  423. }}
  424. />
  425. {selectedRow !== undefined && (
  426. <Modal
  427. title="调度日志详情"
  428. footer={<Button onClick={() => setIsShowDetail(false)}>关闭</Button>}
  429. open={isShowDetail}
  430. onCancel={() => setIsShowDetail(false)}
  431. >
  432. <ProDescriptions column={2}>
  433. <ProDescriptions.Item label="日志序号">
  434. {selectedRow.jobLogId}
  435. </ProDescriptions.Item>
  436. <ProDescriptions.Item
  437. label="任务分组"
  438. valueEnum={{
  439. DEFAULT: {
  440. text: "默认",
  441. status: "DEFAULT",
  442. },
  443. SYSTEM: {
  444. text: "系统",
  445. status: "SYSTEM",
  446. },
  447. }}
  448. >
  449. {selectedRow.jobGroup}
  450. </ProDescriptions.Item>
  451. <ProDescriptions.Item label="任务名称">
  452. {selectedRow.jobName}
  453. </ProDescriptions.Item>
  454. <ProDescriptions.Item label="执行时间">
  455. {selectedRow.createTime}
  456. </ProDescriptions.Item>
  457. </ProDescriptions>
  458. <ProDescriptions column={1}>
  459. <ProDescriptions.Item label="调用目标方法">
  460. {selectedRow.invokeTarget}
  461. </ProDescriptions.Item>
  462. <ProDescriptions column={1}>
  463. <ProDescriptions.Item label="日志信息">
  464. {selectedRow.jobMessage}
  465. </ProDescriptions.Item>
  466. <ProDescriptions.Item
  467. label="执行状态"
  468. valueEnum={{
  469. 0: {
  470. text: "正常",
  471. status: "0",
  472. },
  473. 1: {
  474. text: "暂停",
  475. status: "1",
  476. },
  477. }}
  478. >
  479. {selectedRow.status}
  480. </ProDescriptions.Item>
  481. </ProDescriptions>
  482. </ProDescriptions>
  483. </Modal>
  484. )}
  485. {/* 删除确认模态框 */}
  486. <Modal
  487. title={
  488. <div style={{ display: 'flex', alignItems: 'center' }}>
  489. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  490. <span>系统提示</span>
  491. </div>
  492. }
  493. open={deleteModalVisible}
  494. onOk={executeDeleteRow}
  495. onCancel={cancelDeleteRow}
  496. okText="确认"
  497. cancelText="取消"
  498. >
  499. <p>{`确定删除调度日志编号为“${deleteJobLogId}”的数据项?`}</p>
  500. </Modal>
  501. {/* 清空确认模态框 */}
  502. <Modal
  503. title={
  504. <div style={{ display: 'flex', alignItems: 'center' }}>
  505. <ExclamationCircleFilled style={{ color: '#faad14', marginRight: 8 }} />
  506. <span>系统提示</span>
  507. </div>
  508. }
  509. open={clearAllModalVisible}
  510. onOk={executeClearAll}
  511. onCancel={cancelClearAll}
  512. okText="确认"
  513. cancelText="取消"
  514. >
  515. <p>确定清空所有调度日志数据项?</p>
  516. </Modal>
  517. </PageContainer>
  518. );
  519. }